// SPDX-License-Identifier: MIT
// Copyright (C) 2018-present iced project and contributors

package com.github.icedland.iced.x86.internal.fmt;

import com.github.icedland.iced.x86.Code;
import com.github.icedland.iced.x86.CodeSize;
import com.github.icedland.iced.x86.Instruction;
import com.github.icedland.iced.x86.Register;
import com.github.icedland.iced.x86.fmt.FormatterOptions;
import com.github.icedland.iced.x86.fmt.FormatterOutput;
import com.github.icedland.iced.x86.fmt.FormatterTextKind;
import com.github.icedland.iced.x86.fmt.PrefixKind;

/**
 * DO NOT USE: INTERNAL API
 *
 * @deprecated Not part of the public API
 */
@Deprecated
public final class FormatterUtils {
	private static final String[] spaceStrings = createStrings(' ', 20);
	private static final String[] tabStrings = createStrings('\t', 6);

	private static String[] createStrings(char c, int max) {
		String[] strings = new String[max];
		StringBuilder sb = new StringBuilder();
		for (int i = 0; i < strings.length; i++) {
			sb.append(c);
			strings[i] = sb.toString();
		}
		return strings;
	}

	public static void addTabs(FormatterOutput output, int column, int firstOperandCharIndex, int tabSize) {
		final int max_firstOperandCharIndex = 256;
		if (firstOperandCharIndex < 0)
			firstOperandCharIndex = 0;
		else if (firstOperandCharIndex > max_firstOperandCharIndex)
			firstOperandCharIndex = max_firstOperandCharIndex;

		if (tabSize <= 0) {
			int charsLeft = firstOperandCharIndex - column;
			if (charsLeft <= 0)
				charsLeft = 1;
			addStrings(output, spaceStrings, charsLeft);
		}
		else {
			int endCol = firstOperandCharIndex;
			if (endCol <= column)
				endCol = column + 1;
			int endColRoundedDown = endCol / tabSize * tabSize;
			boolean addedTabs = endColRoundedDown > column;
			if (addedTabs) {
				int tabs = (endColRoundedDown - (column / tabSize * tabSize)) / tabSize;
				addStrings(output, tabStrings, tabs);
				column = endColRoundedDown;
			}
			int spaces = firstOperandCharIndex - column;
			if (spaces > 0)
				addStrings(output, spaceStrings, spaces);
			else if (!addedTabs)
				addStrings(output, spaceStrings, 1);
		}
	}

	private static void addStrings(FormatterOutput output, String[] strings, int count) {
		while (count > 0) {
			int n = count;
			if (n >= strings.length)
				n = strings.length;
			output.write(strings[n - 1], FormatterTextKind.TEXT);
			count -= n;
		}
	}

	public static boolean isCall(int kind) {
		return kind == FormatterFlowControl.NEAR_CALL || kind == FormatterFlowControl.FAR_CALL;
	}

	public static int getFlowControl(Instruction instruction) {
		switch (instruction.getCode()) {
		// GENERATOR-BEGIN: FormatterFlowControlSwitch
		// ⚠️This was generated by GENERATOR!🦹‍♂️
		case Code.JO_REL8_16:
		case Code.JO_REL8_32:
		case Code.JO_REL8_64:
		case Code.JNO_REL8_16:
		case Code.JNO_REL8_32:
		case Code.JNO_REL8_64:
		case Code.JB_REL8_16:
		case Code.JB_REL8_32:
		case Code.JB_REL8_64:
		case Code.JAE_REL8_16:
		case Code.JAE_REL8_32:
		case Code.JAE_REL8_64:
		case Code.JE_REL8_16:
		case Code.JE_REL8_32:
		case Code.JE_REL8_64:
		case Code.JNE_REL8_16:
		case Code.JNE_REL8_32:
		case Code.JNE_REL8_64:
		case Code.JBE_REL8_16:
		case Code.JBE_REL8_32:
		case Code.JBE_REL8_64:
		case Code.JA_REL8_16:
		case Code.JA_REL8_32:
		case Code.JA_REL8_64:
		case Code.JS_REL8_16:
		case Code.JS_REL8_32:
		case Code.JS_REL8_64:
		case Code.JNS_REL8_16:
		case Code.JNS_REL8_32:
		case Code.JNS_REL8_64:
		case Code.JP_REL8_16:
		case Code.JP_REL8_32:
		case Code.JP_REL8_64:
		case Code.JNP_REL8_16:
		case Code.JNP_REL8_32:
		case Code.JNP_REL8_64:
		case Code.JL_REL8_16:
		case Code.JL_REL8_32:
		case Code.JL_REL8_64:
		case Code.JGE_REL8_16:
		case Code.JGE_REL8_32:
		case Code.JGE_REL8_64:
		case Code.JLE_REL8_16:
		case Code.JLE_REL8_32:
		case Code.JLE_REL8_64:
		case Code.JG_REL8_16:
		case Code.JG_REL8_32:
		case Code.JG_REL8_64:
		case Code.JMP_REL8_16:
		case Code.JMP_REL8_32:
		case Code.JMP_REL8_64:
		case Code.VEX_KNC_JKZD_KR_REL8_64:
		case Code.VEX_KNC_JKNZD_KR_REL8_64:
			return FormatterFlowControl.SHORT_BRANCH;
		case Code.LOOPNE_REL8_16_CX:
		case Code.LOOPNE_REL8_32_CX:
		case Code.LOOPNE_REL8_16_ECX:
		case Code.LOOPNE_REL8_32_ECX:
		case Code.LOOPNE_REL8_64_ECX:
		case Code.LOOPNE_REL8_16_RCX:
		case Code.LOOPNE_REL8_64_RCX:
		case Code.LOOPE_REL8_16_CX:
		case Code.LOOPE_REL8_32_CX:
		case Code.LOOPE_REL8_16_ECX:
		case Code.LOOPE_REL8_32_ECX:
		case Code.LOOPE_REL8_64_ECX:
		case Code.LOOPE_REL8_16_RCX:
		case Code.LOOPE_REL8_64_RCX:
		case Code.LOOP_REL8_16_CX:
		case Code.LOOP_REL8_32_CX:
		case Code.LOOP_REL8_16_ECX:
		case Code.LOOP_REL8_32_ECX:
		case Code.LOOP_REL8_64_ECX:
		case Code.LOOP_REL8_16_RCX:
		case Code.LOOP_REL8_64_RCX:
		case Code.JCXZ_REL8_16:
		case Code.JCXZ_REL8_32:
		case Code.JECXZ_REL8_16:
		case Code.JECXZ_REL8_32:
		case Code.JECXZ_REL8_64:
		case Code.JRCXZ_REL8_16:
		case Code.JRCXZ_REL8_64:
			return FormatterFlowControl.ALWAYS_SHORT_BRANCH;
		case Code.CALL_REL16:
		case Code.CALL_REL32_32:
		case Code.CALL_REL32_64:
			return FormatterFlowControl.NEAR_CALL;
		case Code.JMP_REL16:
		case Code.JMP_REL32_32:
		case Code.JMP_REL32_64:
		case Code.JO_REL16:
		case Code.JO_REL32_32:
		case Code.JO_REL32_64:
		case Code.JNO_REL16:
		case Code.JNO_REL32_32:
		case Code.JNO_REL32_64:
		case Code.JB_REL16:
		case Code.JB_REL32_32:
		case Code.JB_REL32_64:
		case Code.JAE_REL16:
		case Code.JAE_REL32_32:
		case Code.JAE_REL32_64:
		case Code.JE_REL16:
		case Code.JE_REL32_32:
		case Code.JE_REL32_64:
		case Code.JNE_REL16:
		case Code.JNE_REL32_32:
		case Code.JNE_REL32_64:
		case Code.JBE_REL16:
		case Code.JBE_REL32_32:
		case Code.JBE_REL32_64:
		case Code.JA_REL16:
		case Code.JA_REL32_32:
		case Code.JA_REL32_64:
		case Code.JS_REL16:
		case Code.JS_REL32_32:
		case Code.JS_REL32_64:
		case Code.JNS_REL16:
		case Code.JNS_REL32_32:
		case Code.JNS_REL32_64:
		case Code.JP_REL16:
		case Code.JP_REL32_32:
		case Code.JP_REL32_64:
		case Code.JNP_REL16:
		case Code.JNP_REL32_32:
		case Code.JNP_REL32_64:
		case Code.JL_REL16:
		case Code.JL_REL32_32:
		case Code.JL_REL32_64:
		case Code.JGE_REL16:
		case Code.JGE_REL32_32:
		case Code.JGE_REL32_64:
		case Code.JLE_REL16:
		case Code.JLE_REL32_32:
		case Code.JLE_REL32_64:
		case Code.JG_REL16:
		case Code.JG_REL32_32:
		case Code.JG_REL32_64:
		case Code.JMPE_DISP16:
		case Code.JMPE_DISP32:
		case Code.VEX_KNC_JKZD_KR_REL32_64:
		case Code.VEX_KNC_JKNZD_KR_REL32_64:
			return FormatterFlowControl.NEAR_BRANCH;
		case Code.CALL_PTR1616:
		case Code.CALL_PTR1632:
			return FormatterFlowControl.FAR_CALL;
		case Code.JMP_PTR1616:
		case Code.JMP_PTR1632:
			return FormatterFlowControl.FAR_BRANCH;
		case Code.XBEGIN_REL16:
		case Code.XBEGIN_REL32:
			return FormatterFlowControl.XBEGIN;
		// GENERATOR-END: FormatterFlowControlSwitch

		default:
			throw new UnsupportedOperationException();
		}
	}

	public static boolean showRepOrRepePrefix(int code, FormatterOptions options) {
		return showRepOrRepePrefix(code, options.getShowUselessPrefixes());
	}

	public static boolean showRepnePrefix(int code, FormatterOptions options) {
		return showRepnePrefix(code, options.getShowUselessPrefixes());
	}

	public static int getSegmentRegisterPrefixKind(int register) {
		assert register == Register.ES || register == Register.CS || register == Register.SS ||
				register == Register.DS || register == Register.FS || register == Register.GS : register;
		return (register - Register.ES) + PrefixKind.ES;
	}

	public static boolean showIndexScale(Instruction instruction, FormatterOptions options) {
		return options.getShowUselessPrefixes() || !Code.ignoresIndex(instruction.getCode());
	}

	public static boolean showSegmentPrefix(int defaultSegReg, Instruction instruction, FormatterOptions options) {
		return showSegmentPrefix(defaultSegReg, instruction, options.getShowUselessPrefixes());
	}

	public static boolean canShowRoundingControl(Instruction instruction, FormatterOptions options) {
		switch (instruction.getCode()) {
		case Code.EVEX_VCVTSI2SD_XMM_XMM_RM32_ER:
		case Code.EVEX_VCVTUSI2SD_XMM_XMM_RM32_ER:
		case Code.EVEX_VCVTDQ2PD_ZMM_K1Z_YMMM256B32_ER:
		case Code.EVEX_VCVTUDQ2PD_ZMM_K1Z_YMMM256B32_ER:
			return options.getShowUselessPrefixes();
		default:
			return true;
		}
	}

	public static boolean isNotrackPrefixBranch(int code) {
		return Integer.compareUnsigned(code - Code.JMP_RM16, 2) <= 0 ||
				Integer.compareUnsigned(code - Code.CALL_RM16, 2) <= 0;
	}

	private static boolean isCode64(int codeSize) {
		return codeSize == CodeSize.CODE64 || codeSize == CodeSize.UNKNOWN;
	}

	private static int getDefaultSegmentRegister(Instruction instruction) {
		int baseReg = instruction.getMemoryBase();
		if (baseReg == Register.BP || baseReg == Register.EBP || baseReg == Register.ESP || baseReg == Register.RBP || baseReg == Register.RSP)
			return Register.SS;
		return Register.DS;
	}

	public static boolean showSegmentPrefix(int defaultSegReg, Instruction instruction, boolean showUselessPrefixes) {
		if (Code.ignoresSegment(instruction.getCode()))
			return showUselessPrefixes;
		int prefixSeg = instruction.getSegmentPrefix();
		assert prefixSeg != Register.NONE : prefixSeg;
		if (isCode64(instruction.getCodeSize())) {
			// ES,CS,SS,DS are ignored
			if (prefixSeg == Register.FS || prefixSeg == Register.GS)
				return true;
			return showUselessPrefixes;
		}
		else {
			if (defaultSegReg == Register.NONE)
				defaultSegReg = getDefaultSegmentRegister(instruction);
			if (prefixSeg != defaultSegReg)
				return true;
			return showUselessPrefixes;
		}
	}

	public static boolean showRepOrRepePrefix(int code, boolean showUselessPrefixes) {
		if (showUselessPrefixes || isRepRepeRepneInstruction(code))
			return true;

		switch (code) {
		// We allow 'rep ret' too since some old code use it to work around an old AMD bug
		case Code.RETNW:
		case Code.RETND:
		case Code.RETNQ:
			return true;

		default:
			return showUselessPrefixes;
		}
	}

	public static boolean showRepnePrefix(int code, boolean showUselessPrefixes) {
		// If it's a 'rep/repne' instruction, always show the prefix
		if (showUselessPrefixes || isRepRepeRepneInstruction(code))
			return true;
		return showUselessPrefixes;
	}

	public static boolean isRepeOrRepneInstruction(int code) {
		switch (code) {
		case Code.CMPSB_M8_M8:
		case Code.CMPSW_M16_M16:
		case Code.CMPSD_M32_M32:
		case Code.CMPSQ_M64_M64:
		case Code.SCASB_AL_M8:
		case Code.SCASW_AX_M16:
		case Code.SCASD_EAX_M32:
		case Code.SCASQ_RAX_M64:
			return true;

		default:
			return false;
		}
	}

	private static boolean isRepRepeRepneInstruction(int code) {
		switch (code) {
		case Code.INSB_M8_DX:
		case Code.INSW_M16_DX:
		case Code.INSD_M32_DX:
		case Code.OUTSB_DX_M8:
		case Code.OUTSW_DX_M16:
		case Code.OUTSD_DX_M32:
		case Code.MOVSB_M8_M8:
		case Code.MOVSW_M16_M16:
		case Code.MOVSD_M32_M32:
		case Code.MOVSQ_M64_M64:
		case Code.CMPSB_M8_M8:
		case Code.CMPSW_M16_M16:
		case Code.CMPSD_M32_M32:
		case Code.CMPSQ_M64_M64:
		case Code.STOSB_M8_AL:
		case Code.STOSW_M16_AX:
		case Code.STOSD_M32_EAX:
		case Code.STOSQ_M64_RAX:
		case Code.LODSB_AL_M8:
		case Code.LODSW_AX_M16:
		case Code.LODSD_EAX_M32:
		case Code.LODSQ_RAX_M64:
		case Code.SCASB_AL_M8:
		case Code.SCASW_AX_M16:
		case Code.SCASD_EAX_M32:
		case Code.SCASQ_RAX_M64:
		case Code.MONTMUL_16:
		case Code.MONTMUL_32:
		case Code.MONTMUL_64:
		case Code.XSHA1_16:
		case Code.XSHA1_32:
		case Code.XSHA1_64:
		case Code.XSHA256_16:
		case Code.XSHA256_32:
		case Code.XSHA256_64:
		case Code.XSTORE_16:
		case Code.XSTORE_32:
		case Code.XSTORE_64:
		case Code.XCRYPTECB_16:
		case Code.XCRYPTECB_32:
		case Code.XCRYPTECB_64:
		case Code.XCRYPTCBC_16:
		case Code.XCRYPTCBC_32:
		case Code.XCRYPTCBC_64:
		case Code.XCRYPTCTR_16:
		case Code.XCRYPTCTR_32:
		case Code.XCRYPTCTR_64:
		case Code.XCRYPTCFB_16:
		case Code.XCRYPTCFB_32:
		case Code.XCRYPTCFB_64:
		case Code.XCRYPTOFB_16:
		case Code.XCRYPTOFB_32:
		case Code.XCRYPTOFB_64:
		case Code.CCS_HASH_16:
		case Code.CCS_HASH_32:
		case Code.CCS_HASH_64:
		case Code.CCS_ENCRYPT_16:
		case Code.CCS_ENCRYPT_32:
		case Code.CCS_ENCRYPT_64:
		case Code.VIA_UNDOC_F30FA6F0_16:
		case Code.VIA_UNDOC_F30FA6F0_32:
		case Code.VIA_UNDOC_F30FA6F0_64:
		case Code.VIA_UNDOC_F30FA6F8_16:
		case Code.VIA_UNDOC_F30FA6F8_32:
		case Code.VIA_UNDOC_F30FA6F8_64:
		case Code.XSHA512_16:
		case Code.XSHA512_32:
		case Code.XSHA512_64:
		case Code.XSTORE_ALT_16:
		case Code.XSTORE_ALT_32:
		case Code.XSTORE_ALT_64:
		case Code.XSHA512_ALT_16:
		case Code.XSHA512_ALT_32:
		case Code.XSHA512_ALT_64:
			return true;

		default:
			return false;
		}
	}
}
